跳到主要内容

Tomcat 的配置相关~

Tomcat 是什么

参考资料 理解Tomcat工作原理 参考资料 民间 Tomcat8 指南

学习了这么久的 javaWeb 知识还不知道 Tomcat 是怎么工作的,只知道它是一个 Servlet 容器,实属惭愧

按功能分类,Web Server可以分为

|- Web Server
|- Http Server
|- Application Server
|- Servlet Container
|- CGI Server
|- ......

注:CGI:Common Gateway Interface 公共网关接口

Http Server:主要用来做静态内容服务、代理服务器、负载均衡等。直面外来请求转发给后面的应用服务(Tomcat 之类的)

Application Server 往往是运行在 HTTP Server 的背后,执行应用,将动态的内容转化为静态的内容之后,通过 HTTP Server 分发到客户端

请求响应对象原理

1、Tomcat 服务器会根据请求 url 中的资源路径创建对应的 Servlet 的对象

2、Tomcat 服务器会创建 Request 和 Response 对象,并在 Request 对象中封装请求消息数据

3、Tomcat 再把创建的 Request 和 Response 对象传递给 Service 方法(doGet 或 doPost)

4、后台通过 Request 取得请求消息数据,再通过 Response 对象设置响应体消息数据,最后把这个响应返回回去

安装 Tomcat

wget https://mirror-hk.koddos.net/apache/tomcat/tomcat-10/v10.0.5/bin/apache-tomcat-10.0.5.tar.gz

tar -zxvf apache-tomcat-10.0.5.tar.gz

配置环境变量,添加 TOMCAT_HOME、TOMCAT_PORT 环境变量

vim ~/.bashrc

# 创建 TOMCAT_HOME 环境变量, 指向 Tomcat 根目录
export TOMCAT_HOME=/home/alsritter/JavaTool/apache-tomcat-10.0.5

# 创建 Tomcat 服务器的端口号环境变量
export TOMCAT_PORT=8080

# PS: 以后 Tomcat 升级版本或目录变更, 默认端口变更, 修改这里即可


# 刷新
source ~/.bashrc

# 检查是否添加成功
echo $TOMCAT_PORT
echo $TOMCAT_HOME

然后每次启动 Tomcat 都需要进到 Tomcat 目录下面执行 startup.bat 有点麻烦,所以,这里可以编写一个脚本来直接启动

创建启动脚本命令

/usr/local/bin 目录下创建一个空的命令文件,命名为 tomcat

sudo touch /usr/local/bin/tomcat

PS: 由于 /usr/local/bin 目录默认就在 PATH 环境变量中,因此存放在 /usr/local/bin 目录下的命令在任何路径下都可以直接执行。

给命令文件添加可执行权限

sudo chmod +x /usr/local/bin/tomcat

编辑命令文件

sudo vim /usr/local/bin/tomcat

添加以下脚本内容到 tomcat 命令到文件中:

#!/bin/sh
DIR="$(cd "$(dirname "$0")" && pwd)"
cd $DIR

# 先判断需要用到的环境变量是否存在
if [ "${TOMCAT_HOME}" = "" ] || [ "${TOMCAT_PORT}" = "" ] ; then
# 需要先配置 TOMCAT_HOME 和 TOMCAT_PORT 环境变量
# export TOMCAT_HOME=Tomcat安装根目录
# export TOMCAT_PORT=Tomcat端口
echo "TOMCAT_HOME or TOMCAT_PORT environment variable not exists!"
echo

else
# 获取命令的第2个参数(第一个参数 $0 是命令自己本身)
OPERATOR="$1"

# 进入到 Tomcat 的命令目录
cd "${TOMCAT_HOME}/bin"

#
# 下面开始根据不同的参数执行相应的命令
#
if [ "${OPERATOR}" = "start" ] ; then
# 执行启动命令
./startup.sh

elif [ "${OPERATOR}" = "restart" ] ; then
# 执行重启命令(先停止, 再启动)
./shutdown.sh
./startup.sh

elif [ "${OPERATOR}" = "stop" ] ; then
# 执行停止命令
./shutdown.sh

elif [ "${OPERATOR}" = "status" ] ; then
# 输出监听状态, 主要是看是否有在监听 Tomcat 端口
netstat -npl | grep ":${TOMCAT_PORT}"

else
# 没有符合的参数, 输出命令格式说明
echo "Usage: tomcat <start|restart|stop|status>"
echo
fi

fi

保存成功后,在终端任何路径下执行命令管理 Tomcat:

#
# tomcat 命令中需要使用到管理员权限, 先切换到管理员账户
#
su
tomcat start # 启动 Tomcat
tomcat restart # 重启 Tomcat
tomcat stop # 停止 Tomcat
tomcat status # 查看 Tomcat 状态

修改默认端口

在Tomcat的根(安装)目录下,有一个 conf 文件夹,双击进入 conf 文件夹,在里面找到 Server.xml 文件,打开该文件。

其次:在文件中找到如下文本:

<Connector port="8080" protocol="HTTP/1.1" 
               maxThreads="150" connectionTimeout="20000" 
               redirectPort="8443" />

<!-- 也有可能是这样的: -->

<Connector port="8080" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" 
disableUploadTimeout="true" />

改成对应端口就行了

添加 API 包

Servlet API 是一个 jar 包,我们需要通过 Maven 来引入它,才能正常编译。

编写 pom.xml 文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itranswarp.learnjava</groupId>
<artifactId>web-servlet-hello</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
</properties>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<finalName>hello</finalName>
</build>
</project>

注意到这个 pom.xml 与普通 Java程序有个区别,打包类型不是 jar,而是 war,表示 Java Web Application Archive:

<packaging>war</packaging>

引入的 Servlet API 如下:

注意到 <scope> 指定为 provided,表示编译时使用,但不会打包到 .war 文件中,因为运行期 Web 服务器本身已经提供了 Servlet API 相关的 jar 包。

我们还需要在工程目录下创建一个 web.xml 描述文件,放到 src/main/webapp/WEB-INF 目录下(固定目录结构,不要修改路径,注意大小写)。

文件内容可以固定如下:

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>

Servlet 的使用

// 只要在Servlet上设置@WebServlet标注,容器就会自动读取当中的信息
@WebServlet("/")
public class TempServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp){
System.out.println("收到请求");
}
}

IDEA 里面创建对象时可以直接创建 Servlet

注:使用 Chrome 请求一次执行两次 doGet 的原因: Chrome 会发送除了请求的那个外,还会执行 /localhost/favicon.ico 这个 url ,上面的那个使用例子的路由是 / 根路径,所以会导致执行两次请求

使用嵌入式 Tomcat 开发

参考资料 Create a Java Web Application Using Embedded Tomcat

上面那种手动丢到 Tomcat的 webapp 目录的方式实在太麻烦了,而且它调试还需要打开 Tomcat 的远程调试端口并且连接上去。

  • 启动 JVM并执行 Tomcat 的 main() 方法;
  • 加载 war 并初始化 Servlet;
  • 正常服务。

启动 Tomcat 无非就是设置好 classpath 并执行 Tomcat 某个 jar 包的 main() 方法,我们完全可以把 Tomcat 的 jar 包全部引入进来,然后自己编写一个 main() 方法,先启动 Tomcat,然后让它加载我们的 webapp 就行。

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itranswarp.learnjava</groupId>
<artifactId>web-servlet-embedded</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
<tomcat.version>9.0.26</tomcat.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>


<!-- 如果要使用 JSP 则需要引入下面两个依赖 -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper-el</artifactId>
<version>${tomcat.version}</version>
</dependency>

<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jsp-api</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>
</project>

引入依赖 tomcat-embed-core 和 tomcat-embed-jasper,引入的 Tomcat 版本 <tomcat.version> 为9.0.26。

不必引入 Servlet API,因为引入 Tomcat 依赖后自动引入了 Servlet API。因此,我们可以正常编写 Servlet 如下:

@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
String name = req.getParameter("name");
if (name == null) {
name = "world";
}
PrintWriter pw = resp.getWriter();
pw.write("<h1>Hello, " + name + "!</h1>");
pw.flush();
}
}

然后,我们编写一个 main() 方法,启动Tomcat服务器:

public class Main {
public static void main(String[] args) throws Exception {
// 启动 Tomcat:
Tomcat tomcat = new Tomcat();
tomcat.setPort(Integer.getInteger("port", 8080));
tomcat.getConnector();
/**
* tomcat.start();
* 到上面这一步其实已经可以把 tomcat 起动了,只是现在启动里面没啥东西
*/

// 创建 webapp:
// 把class加载进来,把启动的工程加入进来了
Context ctx = tomcat.addWebapp("", new File("src/main/webapp").getAbsolutePath());

WebResourceRoot resources = new StandardRoot(ctx);

resources.addPreResources(
new DirResourceSet(resources, "/WEB-INF/classes",
new File("target/classes").getAbsolutePath(), "/")
);

ctx.setResources(resources);
tomcat.start();
tomcat.getServer().await();
}
}

这样,我们直接运行 main() 方法,即可启动嵌入式 Tomcat 服务器,然后,通过预设的 tomcat.addWebapp("", new File("src/main/webapp"),Tomcat 会自动加载当前工程作为根 webapp,可直接在浏览器访问 http://localhost:8080/

启动特别慢的问题

Tomcat启动因为 Creation of SecureRandom instance for session ID generation using [SHA1PRNG] 卡住

这是 JVM 的 BUG

需要在 JVM 启动参数加上:

-Djava.security.egd=file:/dev/./urandom

编码问题

在启动项的 VM 设置里加上 -Dfile.encoding=UTF-8

热部署 Tomcat

每次 IDEA 启动 Tomcat 实际上都是实例化一个新的进程,而用完就释放;这样做效率太低了,所以可以使用热部署来加快开发效率

Run --> Edit Configuration

20200329143440

设置为 On frame deactivation IDE 会失去焦点的情况下自动更新,每次失去焦点就自动触发 update 没必要,设置为 Do nothing 手动更新

Debug 和 Run 的更新策略是不同的

  • 运行模式下(jsp 立即生效,java 需要 redeploy 才可生效)
  • 调试模式下(java、jsp 都立即生效)

war 和 war exploded 区别

war 模式:将 WEB 工程以包的形式上传到服务器;这种可以称之为是发布模式,先打成 war 包,再发布;所以能在 Tomcat 的 webapps 文件夹下看到发布的项目

war exploded 模式:将 WEB 工程以当前文件夹的位置关系上传到服务器;即直接把文件夹、jsp页面 、classes 等等移到 Tomcat 部署文件夹里面,进行加载部署。因此这种方式支持热部署,一般在开发的时候也是用这种方式。(所以并没实际打包 war,因此在 wabapps 文件夹里找不到这个文件夹)